大家好,我是老张。前阵子我有一台跑着个人博客的小服务器,半夜三点突然卡死,等我早上发现的时候,网站已经挂了几个小时。虽然不是什么大业务,但那种“明明睡了却感觉亏了”的心情,相信运维过的朋友都懂。后来我写了个简单的监控脚本,放在服务器上跑着,从此再也没被“突然暴毙”打个措手不及。
今天就把这个脚本分享出来。它用Python标准库实现,不需要装任何第三方依赖,功能很纯粹:每分钟记录一次CPU负载、内存使用、磁盘空间,一旦超过你设定的阈值,就立刻发邮件告警。代码不到100行,有详细注释,你复制过去改几个配置就能用。
为什么不用现成的监控工具?
我知道,很多人会说:有Zabbix、Prometheus、Grafana这些专业监控,为什么还要自己写?确实,这些工具功能强大,但部署和维护成本不低。对于一台轻量级服务器,或者几台小规模的云主机,用Python脚本反而更轻便:没有额外进程,不占资源,改起来也灵活。而且,自己写的脚本,你完全知道它在干什么,出了问题排查也快。
另外,如果你平时用的是像雨云服务器这类性价比不错的云主机,通常自带基础监控面板,但那种监控往往只覆盖CPU和网络,内存、磁盘的细粒度告警还是得自己动手。这个脚本刚好补上这个缺口。
脚本的核心思路
脚本用标准库读取系统信息:
- CPU负载:从
/proc/loadavg读取,取1分钟平均负载,再除以CPU核心数得到“负载百分比”。 - 内存使用:解析
/proc/meminfo,计算已用内存占总量比例。 - 磁盘空间:用
shutil.disk_usage('/')获取根分区使用率。
每项指标都设一个阈值,超过阈值就触发告警。告警方式我写的是发送邮件,你如果想要更即时,可以改成钉钉机器人、企业微信Webhook,或者只是打印到日志里——只要改send_alert函数就行。
完整可运行代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
轻量级服务器监控脚本
功能:每分钟记录CPU、内存、磁盘使用率,超过阈值时发送邮件告警
用法:python3 monitor.py
建议:配合 systemd 或 crontab 实现开机自启/常驻运行
"""
import os
import time
import smtplib
import shutil
from email.mime.text import MIMEText
from datetime import datetime
# ==================== 配置区域 ====================
# 阈值设置(百分比)
CPU_THRESHOLD = 80 # CPU负载超过80%告警
MEM_THRESHOLD = 85 # 内存使用率超过85%告警
DISK_THRESHOLD = 90 # 磁盘使用率超过90%告警
# 告警间隔:同一指标持续超阈值时,最小告警间隔(秒),避免刷屏
ALERT_INTERVAL = 1800 # 30分钟
# 日志文件路径
LOG_FILE = "/var/log/server_monitor.log"
# 邮件配置(修改成你自己的SMTP服务器)
SMTP_HOST = "smtp.example.com"
SMTP_PORT = 465
SMTP_USER = "alert@example.com"
SMTP_PASS = "your_password"
TO_EMAIL = "admin@example.com"
# ==================== 内部状态 ====================
# 记录上次告警时间,用于抑制重复告警
last_alert_time = {
"cpu": 0,
"mem": 0,
"disk": 0
}
def get_cpu_usage():
"""读取 /proc/loadavg,返回CPU 1分钟负载百分比(除以核心数)"""
with open("/proc/loadavg", "r") as f:
loadavg = f.read().split()
load1 = float(loadavg[0]) # 1分钟平均负载
# 获取CPU核心数
cores = os.cpu_count() or 1
# 负载百分比 = 平均负载 / 核心数 * 100(近似)
return load1 / cores * 100
def get_mem_usage():
"""读取 /proc/meminfo,返回内存使用率百分比"""
with open("/proc/meminfo", "r") as f:
lines = f.readlines()
mem_total = 0
mem_available = 0
for line in lines:
if line.startswith("MemTotal:"):
mem_total = int(line.split()[1])
elif line.startswith("MemAvailable:"):
mem_available = int(line.split()[1])
if mem_total and mem_available:
break
# available 是可用内存,所以已用 = total - available
used = mem_total - mem_available
return used / mem_total * 100
def get_disk_usage():
"""使用shutil获取根分区使用率"""
usage = shutil.disk_usage("/")
return usage.used / usage.total * 100
def send_alert(subject, body):
"""发送告警邮件,你可以改成webhook等"""
msg = MIMEText(body, "plain", "utf-8")
msg["Subject"] = subject
msg["From"] = SMTP_USER
msg["To"] = TO_EMAIL
try:
# 如果是SSL SMTP(如465端口)
server = smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT)
server.login(SMTP_USER, SMTP_PASS)
server.sendmail(SMTP_USER, [TO_EMAIL], msg.as_string())
server.quit()
print(f"[{datetime.now()}] 告警邮件发送成功: {subject}")
except Exception as e:
print(f"[{datetime.now()}] 告警邮件发送失败: {e}")
def check_and_alert(name, value, threshold, unit="%"):
"""检查是否超阈值,并按抑制间隔发送告警"""
if value > threshold:
now = time.time()
if now - last_alert_time[name] >= ALERT_INTERVAL:
last_alert_time[name] = now
subject = f"[告警] 服务器{name}使用率过高"
body = (f"服务器告警!\n"
f"指标: {name}\n"
f"当前值: {value:.1f}{unit}\n"
f"阈值: {threshold}{unit}\n"
f"时间: {datetime.now()}\n"
f"请及时处理。")
send_alert(subject, body)
else:
# 恢复正常后,重置上次告警时间,确保下次超阈值能立即告警
last_alert_time[name] = 0
def log_record():
"""将当前指标写入日志文件"""
cpu = get_cpu_usage()
mem = get_mem_usage()
disk = get_disk_usage()
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
line = f"{timestamp} | CPU: {cpu:.1f}% | MEM: {mem:.1f}% | DISK: {disk:.1f}%"
with open(LOG_FILE, "a", encoding="utf-8") as f:
f.write(line + "\n")
print(line)
def main():
"""主循环:每60秒执行一次"""
print("服务器监控脚本已启动,按 Ctrl+C 停止。")
while True:
try:
# 记录日志
log_record()
# 获取当前指标
cpu = get_cpu_usage()
mem = get_mem_usage()
disk = get_disk_usage()
# 逐项检查并告警
check_and_alert("CPU", cpu, CPU_THRESHOLD)
check_and_alert("内存", mem, MEM_THRESHOLD)
check_and_alert("磁盘", disk, DISK_THRESHOLD)
except Exception as e:
# 出错时打印,但不要中断循环
print(f"[{datetime.now()}] 监控异常: {e}")
# 等待60秒
time.sleep(60)
if __name__ == "__main__":
main()
怎么用?
把上面的代码保存为monitor.py,然后:
# 先测试一下能否正常运行python3 monitor.py
如果没问题,用 nohup 后台运行
nohup python3 monitor.py >> /var/log/monitor.log 2>&1 &
我建议用systemd服务来管理,这样即使脚本挂了也能自动重启。你可以写一个/etc/systemd/system/monitor.service,内容大概这样:
[Unit]
Description=Server Monitor
After=network.target
[Service]
ExecStart=/usr/bin/python3 /root/monitor.py
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
然后执行systemctl enable monitor和systemctl start monitor,搞定。
一些经验之谈
这个脚本我跑了快一年,有几个心得想分享:
- 阈值别设太死。比如内存,我设85%,因为有些缓存会临时占用,实际可用空间还在。太敏感了容易误报。
- 告警抑制很关键。如果没有
ALERT_INTERVAL,服务器一旦负载高,每分钟一封邮件,你的邮箱会直接被轰炸。我设置30分钟,既不会漏报,又不会烦人。 - 日志文件记得轮转。用
logrotate定期切割,不然时间长了文件会特别大。我的VPS磁盘才20G,日志攒几个月也有几百M,不处理会拖垮磁盘。 - 监控脚本本身也要监控。如果脚本挂了,系统就“裸奔”了。所以用systemd来守护它,或者加一个crontab每分钟检查进程是否存在。
另外,如果你用的是雨云服务器,这类云厂商的默认监控面板在网页端,虽然也能看,但不会主动推送告警。配合我这个脚本,就能把告警发到邮箱或手机上,真正做到“出了问题第一时间知道”。
最后说两句
这个脚本不算复杂,但非常实用。它解决了一个真实痛点:让你在服务器“生病”之前先收到提醒,而不是等用户骂上门才去救火。如果你有更多需求,比如监控网络流量、进程数、连接数,完全可以在这个基础上扩展。毕竟,脚本是死的,思路是活的。
希望这个脚本能帮你少熬几个夜。如果你有更好的监控方案,欢迎在评论区聊聊,我也顺便学学。下次见!
评论区: